var net = require("net");
module.exports = MCProtocol;

function MCProtocol(param) {
  let self = this;

  self.name = param.name || "";
  self.connectionTarget = {
    host: param.host || "192.168.30.1",
    port: param.port || 6200,
  };
  self.frame = param.frame || "3E";
  //暂时只写了二进制传输报文
  self.transmissionFormate = "binary" || param.formate;
  self.logLevel = param.logLevel || 0;
  self.packetTimeout = param.timeout || 4000;

  self._socket = undefined;
  self.connectionState = 0;
  self.timeoutInfo = "";
  self._timeoutId = undefined;
  self.isReading = false;
  self.isWriting = false;
  self.sendPacketArray = [];
  self.currentOperateIndex = 0;
  //[{addr:d2,action:write/read,length:0,value:0},{addr:d2,action:write/read,value:12,length:0,value:0}]
}

MCProtocol.prototype.log = function (content, debugLevel) {
  let self = this,
    idtext;
  self.name ? (idtext = self.name) : (idtext = self.connectionTarget.host);
  if (self.logLevel >= debugLevel) {
    console.log("[" + idtext + " " + new Date().getTime() + "] " + content);
  }
};

MCProtocol.prototype.connect = function (callback) {
  let self = this;
  //需要判断当前连接状态
  if (self.connectionState >= 1) {
    return;
  }

  self.connectionState = 0;
  self.isReading = false;
  self.isWriting = false;
  if (typeof self._socket !== "undefined") {
    self._socket.removeAllListeners("data");
    self._socket.removeAllListeners("error");
    self._socket.removeAllListeners("close");
    self._socket.removeAllListeners("connect");
  }

  self._socket = new net.Socket();
  self._socket.setEncoding("hex");
  // 长连接,每1分钟空闲后发送连接确认
  self._socket.setKeepAlive(true, 60000);

  self._socket.connect(
    self.connectionTarget.port,
    self.connectionTarget.host,
    function () {
      //连接成功后
      clearTimeout(self._timeoutId);

      self.log(
        "The tcp connection to " +
          self.connectionTarget.host +
          " on port " +
          self.connectionTarget.port +
          " has been succeeded",
        0
      );
      self.connectionState = 4; // 4 = already connected

      self._socket.on("data", function () {
        //解析响应数据
        self.onResponse.apply(self, arguments);
      });

      callback && callback();
      //  500000FFFF03001000100001140000640000900200 4723 96AB
      //  500000FFFF03000C00100001040000640000900200
      //  03FF10006400000020440300341276980901  1E 83 00
      self.sendRead("");
    }
  );

  self.connectionState = 1; // 1 = trying to connect

  // 连接超时
  self.timeoutInfo = "initial connection";
  self._timeoutId = setTimeout(function () {
    self.log(self.timeoutInfo, " timed out !!");
    self.connectionState = 0;
    self._socket.end();
  }, self.packetTimeout * 5);

  //连接出错
  self._socket.on("error", function (e) {
    self.log("Error " + e.code + " occured when " + self.timeoutInfo || "", 0);
    self.connectionState = 0;
    self.isReading = false;
    self.isWriting = false;
    self._timeoutId && clearTimeout(self._timeoutId);
  });

  //连接断开
  self._socket.on("close", function () {
    self.log("Connection closed when " + self.timeoutInfo || "", 0);
    self.connectionState = 0;
    self.isReading = false;
    self.isWriting = false;
    self._timeoutId && clearTimeout(self._timeoutId);
  });

  self.log(
    "Initiating a new connection, attempting to connect to " +
      self.connectionTarget.host +
      " on port " +
      self.connectionTarget.port,
    0
  );
};

MCProtocol.prototype.onResponse = function (resData) {
  var self = this;
  var data;

  if (self.transmissionFormate !== "binary") {
    data = binarize(resData);
    if (typeof data === "undefined") {
      self.log(
        "Failed ASCII conversion to binary on reply. Ignoring packet.",
        1
      );
      return null;
    }
  } else {
    data = resData;
  }

  if (data.length < 2) {
    self.log("DATA LESS THAN 2 BYTES RECEIVED.  NO PROCESSING WILL OCCUR");
    self.log(data, 2);
    return null;
  }

  if (data.length < 11 && self.frame == "3E") {
    self.log(
      "DATA LESS THAN 11 BYTES RECEIVED FOR 3E FRAME.  NO PROCESSING WILL OCCUR"
    );
    self.log(data, 2);
    return null;
  }

  self.log("Received " + data.length + " bytes of data from PLC.", 1);

  if (self.isReading) {
    self.log("Received Read Response", 1);
    self.readResponse(data);
  }

  if (self.isWriting) {
    self.log("Received Write Response", 1);
    self.writeResponse(data);
  }

  if (!self.isReading && !self.isWriting) {
    self.log("Unexpected packet arrived wasn't a write or read reply");
    self.log(data, 0);
    return null;
  }
};

MCProtocol.prototype.readResponse = function (data) {
  //D00000FFFF030006000000 34 12 02 00
  this.isReading = false;
  this.log(data, 2);
};

MCProtocol.prototype.writeResponse = function (data) {
  //D00000FFFF030002000000
  this.isWriting = false;
  this.log(data, 2);
};

MCProtocol.prototype.sendWrite = function (str) {
  let self = this;
  if (self.isReading || this.isWriting) return null;
  self.isWriting = true;
  self._socket.write(str2Buffer(str));
  self.log("Writing ", str, 2);
};

MCProtocol.prototype.sendRead = function (str) {
  let self = this;
  if (self.isReading || this.isWriting) return null;
  self.isReading = true;
  self._socket.write(str2Buffer(str));
  self.log("Reading ", str, 2);
};

MCProtocol.prototype.read = function (addr, length) {};

MCProtocol.prototype.write = function (addr, length, valueArray) {
  //Subheader    Network No.   PC No.   Request destination module I/O No.   module station No.
  //5000            00          FF                 FF03                          00  

  //Request data length(16)    onitoring timer (4S)  Command  Subcommand   Head device number(100)
  //1000                        1000                  0114      0000         64 00 00
                                              // SubCommand Q/L '0000' / iQ-R '0200'(binary)

  //Device code(M)    Number of device points(2)       Write data
  //90                0200                             4723  96AB

  //03 FF 1000 640000 0020440300341276980901

  let subheader = "5000",
    network = "00FFFF0300",
    dataLen,
    onitoringTimer = "1000",
    command = "0114",
    subcommand = "0000",
    headDeviceNo,
    deviceCode,
    deviceByteLen,
    writeData;

  let reg = /([A-Z]+)(\d+)/gi,
    deviceType;
  deviceInfo;

  if (reg.test(addr)) {
    deviceType = RegExp.$1;
    addr = RegExp.$2;
  } else {
    return null;
  }

  deviceInfo = getDeviceInfo(deviceType.toUpperCase());

  
  if (!deviceInfo) return null;
  deviceCode = deviceInfo.slice(0, 2);

  switch (deviceInfo.slice(2)) {
    case "B":
      deviceByteLen = valueArray.length / 16;
      break;
    case "W":
      deviceByteLen = valueArray.length;
      break;
    case "D":
      deviceByteLen = valueArray.length * 2;
      break;
    default:
      return null;
  }

  this.sendWrite(
    subheader +
      network +
      dataLen +
      onitoringTimer +
      command +
      subcommand +
      headDeviceNo +
      deviceCode +
      deviceByteLen +
      writeData
  );
};

function str2Buffer(str) {
  return new Buffer.from(str, "hex");
}

function binarize(buf) {
  var i, newBuf;
  if (buf && !(buf.length % 2)) {
    newBuf = new Buffer.alloc(buf.length / 2);
    for (i = 0; i < buf.length; i += 2) {
      newBuf[i / 2] = parseInt("0x" + buf.toString("ascii", i, i + 2));
      if (isNaN(newBuf[i / 2])) {
        return undefined;
      }
    }
    return newBuf;
  }
  return undefined;
}

function getDeviceInfo(deviceType) {
  switch (deviceType) {
    case "SM":
      return "91" + "B";
    case "SD":
      return "A9" + "W";
    case "X":
      return "9C" + "B";
    case "Y":
      return "9D" + "B";
    case "M":
      return "90" + "B";
    case "L":
      return "92" + "B";
    case "F":
      return "93" + "B";
    case "V":
      return "94" + "B";
    case "B":
      return "A0" + "B";
    case "D":
      return "A8" + "W";
    case "W":
      return "B4" + "W";
    case "SB":
      return "A1" + "B";
    case "SW":
      return "B5" + "W";
    case "S":
      return "98" + "B";
    case "DX":
      return "A2" + "B";
    case "DY":
      return "A3" + "B";
    case "Z":
      return "CC" + "W";
    case "LZ":
      return "62" + "D";
    case "R":
      return "AF" + "W";
    case "ZR":
      return "B0" + "W";
    case "RD":
      return "2C" + "W";

    case "TS":
      return "C1" + "B";
    case "TC":
      return "C0" + "B";
    case "TN":
      return "C2" + "W";

    case "LTS":
      return "51" + "B";
    case "LTC":
      return "50" + "B";
    case "LTN":
      return "52" + "D";

    case "STS":
      return "C7" + "B";
    case "STC":
      return "C6" + "B";
    case "STN":
      return "C8" + "W";

    case "LSTS":
      return "59" + "B";
    case "LSTC":
      return "58" + "B";
    case "LSTN":
      return "5A" + "D";

    case "CS":
      return "C4" + "B";
    case "CC":
      return "C3" + "B";
    case "CN":
      return "C5" + "W";

    case "LCS":
      return "55" + "B";
    case "LCC":
      return "54" + "B";
    case "LCN":
      return "56" + "D";

    default:
      return undefined;
  }
}

